/*
**  File: Range.c
**
**  AUTHOR(S) : Roman Laskowski
*/

#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>

/* Flag-values */
#define FALSE           0
#define TRUE            1
#define MXTOKN          6
#define RECLEN         80
#define TOKLEN          5


#include "Range.h"


/***********************************************************************

get_tokens  -  Extract all the token_strings (ie separate 
                    char-strings) from the line just read in

***********************************************************************/

int get_tokens(char irec[RECLEN + 1],char token_string[MXTOKN][TOKLEN + 1],
	       char token_chain[MXTOKN])
{
/*  char ch; */
  char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  char lower[] = "abcdefghijklmnopqrstuvwxyz";
  int end_line, found, ipos, ipstn, j, token, n_tokens; /*  int ivalue; */


  /* Initialise variables for extraction of information from
     the line just read in */
  end_line = FALSE;
  ipstn = 0;
  ipos = 0;
  for (token = 0; token < MXTOKN; token++)
    {
      strncpy(token_string[token],"      ",TOKLEN);
      token_string[token][TOKLEN] = '\0';
      token_chain[token] = ' ';
    }
  token = -1;

  /* Search the line for each token_string and store */
  for (ipos = 0; ipos < RECLEN && end_line == FALSE; ipos++)
    {
      /* Check for end of line or end of string */
      if (irec[ipos] == '\n' || irec[ipos] == '\0')
	end_line = TRUE;

      /* If have a space, then check if have just ended a token_string */
      else if (irec[ipos] == ' ' || irec[ipos] == '\t')
	{
	  /* If currently saving a token_string, then have reached its end */
	  ipstn = 0;
	}

      /* Otherwise, if this is a character, then store in the
         current token_string */
      else
	{
	  /* If this is the start of a new token_string, increment
             token_string-count */
	  if (ipstn == 0)
	    token = token + 1;

	  /* Process providing we haven't exceeded number of token_strings
	     allowed */
	  if (token < MXTOKN)
	    {
	      /* Convert the character into upper-case if necessary */
	      found = FALSE;
	      for (j = 0; j < 26 && found == FALSE; j++)
		{
		  if (irec[ipos] == lower[j])
		    {
		      found = TRUE;
		      irec[ipos] = upper[j];
		    }
		}

	      /* Store the current character in the token_string */
	      if (ipstn < TOKLEN)
		token_string[token][ipstn] = irec[ipos];

	      /* Increment position in the current token_string */
	      ipstn = ipstn + 1;
	    }
	}
    }

  /* Save the number of token_strings read in fom this line */
  n_tokens = token + 1;
  if (n_tokens > MXTOKN)
    n_tokens = MXTOKN;
  return(n_tokens);
}
/***********************************************************************

interpret_tokens  -  Interpret the current line's token_strings

***********************************************************************/

int interpret_tokens(char token_string[MXTOKN][TOKLEN + 1],
		      char token_chain[MXTOKN],int n_tokens)
{
  char ch, chain, dummy[TOKLEN + 1];
  int ipos, ipstn, is_model, token, free_token, last_token;
/*  int ilen, ivalue; */
  
  /* Initialise variables for interpretation of token_strings */
  chain = ' ';
  last_token = -1;
  is_model = FALSE;
  strncpy(dummy,"     ",TOKLEN);
  dummy[TOKLEN] = '\0';

  /* Check whether this is a MODEL range */
  if (!strncmp(token_string[0],"MODEL",TOKLEN))
    {
      is_model = TRUE;
      token_chain[0] = 'X';
      strncpy(token_string[0],"XXXXX",TOKLEN);
    }
  
  /* If this is a residue line, then mark token_string for deletion */
  if (!strncmp(token_string[0],"RES",3))
    {
      token_chain[0] = 'X';
      strncpy(token_string[0],"XXXXX",TOKLEN);
    }

  /* Process all the stored token_strings */
  for (token = 0; token < n_tokens; token++)
    {
      /* Check if this is a chain identifier */
      ch = token_string[token][0];
      if (token_string[token][1] == ' ' && ch >= 'A' && ch <= 'Z')
	{
	  chain = ch;

	  /* If this is the first token_string of this chain, treat
	     as though an "ALL" entry */
	  if (token - last_token == 1)
	    {
	      strncpy(token_string[token],"ALL  ",TOKLEN);
	      token_chain[token] = chain;
	    }
	  
	  /* Otherwise, update all prior token_strings with this chain */
	  else
	    {
	      for (free_token = last_token + 1; free_token < token;
		   free_token++)
		{
		  if (token_chain[free_token] == ' ')
		    token_chain[free_token] = chain;
		}

	      /* Mark the current token_string for deletion */
	      token_chain[token] = 'X';
	      strncpy(token_string[token],"XXXXX",TOKLEN);
	    }
	  last_token = token;
	}

      /* If it is one of the Reserved words, then leave alone */
      else if (!strncmp(token_string[token],"ALL  ",TOKLEN) ||
	       !strncmp(token_string[token],"FIRST",TOKLEN) ||
	       !strncmp(token_string[token],"LAST ",TOKLEN))
	{
	}

      /* If this is a 'TO' string, then just mark for deletion */
      else if (!strncmp(token_string[token],"TO   ",TOKLEN))
	{
	  token_chain[token] = 'X';
	  strncpy(token_string[token],"XXXXX",TOKLEN);
	}

      /* Otherwise, assume this is a residue- or model-number, so
         shift it into the correct column positions */
      else
	{
	  /* Right-justify the token_string string */
	  ipstn = TOKLEN - 1;
	  for (ipos = TOKLEN - 1; ipos >= 0; ipos--)
	    {
	      dummy[ipos] = ' ';
	      if (token_string[token][ipos] != ' ')
		{
		  dummy[ipstn] = token_string[token][ipos];
		  ipstn--;
		}
	    }
	  strncpy(token_string[token],dummy,TOKLEN);

	  /* If this is not a model-number, or the last character is
             a letter of an insertion code, take that to be an
             insertion code, so keep whole token_string right-justified */
	  ch = token_string[token][TOKLEN - 1];
	  if (is_model || (ch >= 'A' && ch <= 'Z'))
	    {
	    }

	  /* Otherwise, shift back a character */
	  else
	    {
	      for (ipos = 0; ipos < TOKLEN - 1; ipos++)
		{
		  dummy[ipos] = token_string[token][ipos + 1];
		}
	      dummy[TOKLEN - 1] = ' ';
	      strncpy(token_string[token],dummy,TOKLEN);
	    }
	}
    }

  return(is_model);
}
/***********************************************************************

delete_unwanted_tokens  -  Delete unwanted token_strings

***********************************************************************/

int delete_unwanted_tokens(char token_string[MXTOKN][TOKLEN + 1],
			    char token_chain[MXTOKN],int n_tokens)
{
  int new_tokens, token, free_token;

  /* Initialise location of next free token_string */
  free_token = 0;

  /* Loop through all the token_strings, overwriting any deleted ones */
  for (token = 0; token < n_tokens; token++)
    {
      /* If token_string a valid one, then move to next available spot */
      if (strncmp(token_string[token],"XXXXX",TOKLEN))
	{
	  if (free_token < token)
	    {
	      strncpy(token_string[free_token],token_string[token],TOKLEN);
	      token_chain[free_token] = token_chain[token];
	    }
	  free_token = free_token + 1;
	}
    }

  /* Save the adjusted number of token_strings stored */
  new_tokens = free_token;
  if (new_tokens > MXTOKN)
    new_tokens = MXTOKN;

  return(new_tokens);
}
/***********************************************************************
store_tokens  -  Store all the token_strings representing current model-
                 or residue-range
***********************************************************************/

void store_tokens(char token_string[MXTOKN][TOKLEN + 1],
		  char token_chain[MXTOKN],int n_tokens,int is_model,
		  char filename[FILENAME_LEN],char irec[RECLEN],
		  int line,int first_model[MAXRNG],int last_model[MAXRNG],
		  char first_residue[MAXRNG][7],char last_residue[MAXRNG][7],
		  int *nresidue_ranges,int *nmodel_ranges)
{
/*  char ch; */
  int n_mod_range, n_res_range, ival1, ival2;
  int valid;
/*  int token; */
/*  char chain; */
    
  /* Initialise values */
  n_mod_range = *nmodel_ranges;
  n_res_range = *nresidue_ranges;

  /* If the token_strings are for a model range, then store first and last
     model numbers */
  if (is_model == TRUE && n_tokens > 0)
    {
      valid = TRUE;

      /* If all models required, then set range as all-encompassing */
      if (!strncmp(token_string[0],"ALL  ",TOKLEN))
	{
	  ival1 = -99999;
	  ival2 = 99999;
	  first_model[0] = ival1;
	  last_model[0] = ival2;
	}

      /* Otherwise, interpret model-number range */
      else
	{
	  /* If FIRST model defined, store start */
	  if (!strncmp(token_string[0],"FIRST",TOKLEN))
	    ival1 = -99999;
	  else
	    {
/* Original:    if (sscanf(token_string[0],"%5d",&ival1) == NULL) */
	      if (sscanf(token_string[0],"%5d",&ival1) != 1)
		{
                  printf("*** Warning. Invalid model number:");
		  printf(" [%s] in line %d of file %s\n",token_string[0],
			 line,filename);
                  printf("***          line ignored: [%s]\n",irec);
                  valid = FALSE;
		}
	    }

	  /* Interpret end model-number of range */
	  
	  /* If LAST model defined, store end */
	  if (!strncmp(token_string[1],"LAST ",TOKLEN))
	    ival2 = 99999;
	  
	  /* If no second model number, then range consists of a
	     single model number */
	  else if (!strncmp(token_string[1],"XXXXX",TOKLEN))
	    ival2 = ival1;

	  /* Otherwise assume it is a model number */
	  else
	    {
/* Original:    if (sscanf(token_string[1],"%5d",&ival2) == NULL) */
	      if (sscanf(token_string[1],"%5d",&ival2) != 1)
		{
		  printf("*** Warning. Invalid model number:");
		  printf(" [%s] in line %d of file %s\n",token_string[1],
			 line,filename);
		  printf("***          line ignored: [%s]\n",irec);
		  valid = FALSE;
		}
	    }
	}

      /* If have a valid range, then store */
      if (valid == TRUE)
	{
	  if (n_mod_range > MAXRNG)
	    {
	      printf("**** Maximum number of model ranges ");
	      printf("exceeded in file %s, MAXRNG = %d\n",
		     filename,MAXRNG);
	      exit(1);
	    }
	  first_model[n_mod_range] = ival1;
	  last_model[n_mod_range] = ival2;
	  n_mod_range = n_mod_range + 1;
	}
    }

  /* Otherwise, store the residue range */
  else if (n_tokens > 0)
    {
      if (n_res_range > MAXRNG)
	{
	  printf("**** Maximum number of residue ranges ");
	  printf("exceeded in file %s, MAXRNG = %d\n",
		 filename,MAXRNG);
	  exit(1);
	}
      /* First residue of range */
      first_residue[n_res_range][0] = token_chain[0];
      first_residue[n_res_range][1] = '\0';
      strncat(first_residue[n_res_range],token_string[0],TOKLEN);
      first_residue[n_res_range][TOKLEN + 1] = '\0';

      /* Last residue of range */
      last_residue[n_res_range][0] = token_chain[1];
      last_residue[n_res_range][1] = '\0';
      strncat(last_residue[n_res_range],token_string[1],TOKLEN);
      last_residue[n_res_range][TOKLEN + 1] = '\0';
      n_res_range = n_res_range + 1;
    }

  /* Store number of model and residue ranges */
  *nresidue_ranges = n_res_range;
  *nmodel_ranges = n_mod_range;
}
/***********************************************************************

print_ranges  -  Print out the model- and residue ranges

***********************************************************************/

void print_ranges(char filename[FILENAME_LEN],int first_model[MAXRNG],
		  int last_model[MAXRNG],char first_residue[MAXRNG][7],
		  char last_residue[MAXRNG][7],int nresidue_ranges,
		  int nmodel_ranges)
{
  char dummy[RECLEN], irec[RECLEN];
  int range; /*  int ilen; */
  int valid;

  /* Print heading */
  printf("*\n");
  printf("*\n");
  printf("*  Model and residue ranges selected in file: %s\n",filename);
  printf("*  ------------------------------------------\n");
  printf("*\n");

  /* Model-number range */
  if (first_model[0] == -99999 && last_model[0] == 99999)
    printf("*      ALL models in the ensemble\n");
  else
    {
      for (range = 0; range < nmodel_ranges; range++)
	{
	  /* Retrieve start of model-number range */
	  if (first_model[range] == -99999)
	    strcpy(irec,"From FIRST model");
	  else
	    sprintf(irec,"From model %5d",first_model[range]);

	  /* Retrieve end of model-number range */
	  if (last_model[range] == 99999)
	    strcat(irec," to LAST model");
	  else
	    {
/* Original code:
	      sprintf(dummy," to model %5d\0",last_model[range]); */

	      sprintf(dummy," to model %5d",last_model[range]);
	      strcat(irec,dummy);
	    }

	  /* Show the interpreted range */
	  printf("*      %s\n",irec);
	}
    }
  printf("*\n");

  /* Residue-number range */
  if (!strncmp(first_residue[0],"*ALL  ",TOKLEN + 1))
    printf("*      ALL residues\n");
  else
    {
      for (range = 0; range < nresidue_ranges; range++)
	{
	  /* Retrieve start of residue-number range */
	  if (!strncmp(first_residue[range]+1,"ALL  ",TOKLEN))
	    {
	      strcpy(irec,"All residues");
	    }
	  else if (!strncmp(first_residue[range]+1,"FIRST",TOKLEN))
	    {
	      strcpy(irec,"From FIRST residue");
	    }
	  else
	    {
/* Original code:
	      sprintf(irec,"From residue %s\0",first_residue[range]+1); */
	      sprintf(irec,"From residue %s",first_residue[range]+1);
	    }

	  /* Add chain identifier, if required */
	  if (first_residue[range][0] != ' ')
	    {
/* Original code:
	      sprintf(dummy," in chain [%c]\0",first_residue[range][0]); */
	      sprintf(dummy," in chain [%c]",first_residue[range][0]);
	      strcat(irec,dummy);
	    }

	  /* Retrieve end of residue-number range */
	  valid = FALSE;
	  if (!strncmp(last_residue[range]+1,"LAST ",TOKLEN))
	    {
	      strcat(irec," to LAST residue");
	      valid = TRUE;
	    }
	  else if (strncmp(last_residue[range]+1,"XXXXX",TOKLEN))
	    {
/* Original code:
	       sprintf(dummy," to residue %s\0",last_residue[range]+1); */
	       sprintf(dummy," to residue %s",last_residue[range]+1);
	       strcat(irec,dummy);
	       valid = TRUE;
	     }

	  /* Add chain identifier, if required */
	  if (valid == TRUE && last_residue[range][0] != ' ')
	    {
/* Original code:
	      sprintf(dummy," in chain [%c]\0",last_residue[range][0]); */
	      sprintf(dummy," in chain [%c]",last_residue[range][0]);
	      strcat(irec,dummy);
	    }

	  /* Show the interpreted range */
	  printf("*      %s\n",irec);
	}
      printf("*\n");
    }
}
/***********************************************************************

get_ranges  -  Read in the list of residue ranges to be included
                        in the outputs
***********************************************************************/

void get_ranges(char filename[FILENAME_LEN],int first_model[MAXRNG],
		int last_model[MAXRNG],char first_residue[MAXRNG][7],
		char last_residue[MAXRNG][7],int *nmodel_ranges,
		int *nresidue_ranges,int *both_ends,int *ifail)
{
  char token_chain[MXTOKN];
  char token_string[MXTOKN][TOKLEN + 1];
  char irec[RECLEN + 1];
  int ipos;
  int line, n_tokens, new_tokens;
  int n_mod_range, n_res_range;
  int is_model;
/*  char chain; */
/*  int valid, last_token, free_token, ival1, ival2, ivalue ; */
/*  int ilen, range, token; */
  
  FILE *fil_rng;

  /* Initialise variables */
  *both_ends = FALSE;
  *ifail = FALSE;
  line = 0;
  n_mod_range = 0;
  n_res_range = 0;

  /* Open input file defining the ranges of residue of interest */
  if ((fil_rng = fopen(filename,"r")) == NULL)
    {
      printf("\n*** Unable to open input ranges file: %s\n\n",filename);
      exit(1);
    }

  /* Read through the file */
  while (fgets(irec,RECLEN,fil_rng)!= NULL)
    {
      /* Increment line-count */
      line = line + 1;

      /* Get first character to check that line hasn't been commented out */
      if (irec[0] != '%' && irec[0] != '#')
	{
          /* Check whether this line contains the BOTH keyword */
          for (ipos = 0; ipos < RECLEN - 4; ipos++)
            {
              if (!strncmp(irec+ipos,"BOTH",4))
                {
                  *both_ends = TRUE;
                  irec[0] = '%';
                }
            }
        }

      /* Process the line to extract the model- and residue-ranges */
      if (irec[0] != '%' && irec[0] != '#')
        {
	  /* Extract all the token_strings (ie separate character-strings)
             from the line just read in */
	  n_tokens = get_tokens(irec,token_string,token_chain);

	  /* Interpret the token_strings, modifying them accordingly */
	  is_model = interpret_tokens(token_string,token_chain,n_tokens);

	  /* Delete any unwanted token_strings (marked for deletion with
	     XXXXX) */
	  new_tokens = delete_unwanted_tokens(token_string,token_chain,
					      n_tokens);
	  n_tokens = new_tokens;

	  /* Store all the token_strings representing current model- or
	     residue-range */
	  store_tokens(token_string,token_chain,n_tokens,is_model,
		       filename,irec,line,first_model,last_model,
		       first_residue,last_residue,&n_res_range,
		       &n_mod_range);
	  if (*ifail == TRUE)
	    exit(1);
	}
    }

  /* Close the residue ranges file */
  fclose(fil_rng);

  /* If no model ranges defined, assume that all models wanted */
  if (n_mod_range == 0)
    {
      n_mod_range = 1;
      first_model[0] = -99999;
      last_model[0] = 99999;
    }

  /* If no residue ranges defined, assume that all residues wanted */
  if (n_res_range == 0)
    {
      n_res_range = 1;
      strncpy(first_residue[0],"*ALL ",TOKLEN);
      strncpy(last_residue[0],"XXXXX",TOKLEN);
    }

  /* Print the model- and residue-ranges selected */
  print_ranges(filename,first_model,last_model,first_residue,
	       last_residue,n_res_range,n_mod_range);


  /* Store number of model and residue ranges */
  *nresidue_ranges = n_res_range;
  *nmodel_ranges = n_mod_range;
}
/***********************************************************************

check_model_ranges  -  Check whether supplied model-number is within one
                       or more of the ranges defined by the user

***********************************************************************/

int check_model_ranges(int current_model,int first_model[MAXRNG],
		       int last_model[MAXRNG],int nmodel_ranges)
{
  int range;
  int match_first, match_last, wanted;

  /* Initialise */
  wanted = FALSE;

  /* Loop through all the defined model ranges and check model number
     against each one */
  for (range = 0; range < nmodel_ranges; range++)
    {
      /* Initialise flags for this range (first and last parameters) */
      match_first = FALSE;
      match_last = FALSE;

      /* Check for lower-bound */
      if (current_model >= first_model[range])
	match_first = TRUE;

      /* Check for upper-bound */
      if (current_model <= last_model[range])
	match_last = TRUE;

      /* If model satisfies both the first and last parameters, then
         it is wanted */
      if (match_first == TRUE && match_last == TRUE)
	wanted = TRUE;
    }

  /* Return the result */
  return(wanted);
}
/***********************************************************************

check_residue_ranges  -  Check whether supplied residue is within one or
                         more of the ranges defined by the user

***********************************************************************/

int check_residue_ranges(char residue_number[6],char chain,
			 char first_residue[MAXRNG][7],
			 char last_residue[MAXRNG][7],int nresidue_ranges)
{
  char ch;
  char dummy[6], residue_id[7];
  int ipos, ipstn, range;
  int match_first, match_last, wanted;
/*  int ilen; */

  /* If all residues selected, then don't need to perform any more tests */
  if (!strncmp(first_residue[0],"*ALL",4))
    wanted = TRUE;

  /* Otherwise, format the entered residue_idue-number */
  else
    {
      /* Right-justify the entered residue-number */
      ipstn = 4;
      for (ipos = 4; ipos >= 0; ipos--)
	{
	  dummy[ipos] = ' ';
	  if (residue_number[ipos] != ' ')
	    {
	      dummy[ipstn] = residue_number[ipos];
	      ipstn--;
	    }
	}
      dummy[5] = '\0';
      strncpy(residue_number,dummy,5);
      residue_number[5] = '\0';
      
      /* If the last character is a letter, take that to be an insertion
	 code, so keep whole residue-number right-justified */
      ch = residue_number[4];
      if (ch >= 'A' && ch <= 'Z')
	{
	}

      /* Otherwise, shift back a character */
      else
	{
	  for (ipos = 0; ipos < 4; ipos++)
	    {
	      dummy[ipos] = residue_number[ipos + 1];
	    }
	  dummy[4] = ' ';
	  dummy[5] = '\0';
	  strncpy(residue_number,dummy,5);
	}
      residue_number[5] = '\0';
      
      /* Initialise */
      wanted = FALSE;
      residue_id[0] = chain;
      residue_id[1] = '\0';
      strcat(residue_id,residue_number);
      residue_id[6] = '\0';
      
      /* Loop through all the defined residue ranges and check residue number
	 and chain against each one */
      for (range = 0; range < nresidue_ranges; range++)
	{
	  /* Initialise flags for this range (first and last parameters) */
	  match_first = FALSE;
	  match_last = FALSE;
	  
	  /* Check for ALL of a given chain */
	  if (!strncmp(first_residue[range]+1,"ALL  ",TOKLEN))
	    {
	      /* Check that is the correct chain */
	      if (chain == first_residue[range][0])
		{
		  match_first = TRUE;
		  match_last = TRUE;
		}
	    }
	  
	  /* Otherwise, check whether range is from FIRST residue */
	  else if (!strncmp(first_residue[range]+1,"FIRST",TOKLEN))
	    {
	      /* Check that this residue is of a higher chain than that
		 stated */
	      if (chain >= first_residue[range][0])
		match_first = TRUE;
	    }
	  
      /* Otherwise check that the residue number is indeed higher
	 than (or equal to) the number defining the start of the
	 range */
	  else
	    {
	      if (strncmp(residue_id,first_residue[range],TOKLEN) >= 0)
		match_first = TRUE;
	    }
	  
	  /* Check whether range-end is from LAST residue */
	  if (!strncmp(last_residue[range]+1,"LAST ",TOKLEN))
	    {
	      /* Check that this residue is of a lower chain than that
		 stated */
	      if (chain <= last_residue[range][0])
		match_last = TRUE;
	    }
	  
	  /* If only a single residue defined, check whether this is the
	     one */
	  else if (!strncmp(last_residue[range]+1,"XXXXX",TOKLEN))
	    {
	      if (strncmp(residue_id,first_residue[range],TOKLEN) <= 0)
		match_last = TRUE;
	    }
	  
	  /* Otherwise check that the residue number is lower
	     than (or equal to) the number defining the end of the
	     range */
	  else
	    {
	      if (strncmp(residue_id,last_residue[range],TOKLEN) <= 0)
		match_last = TRUE;
	    }
	  
	  /* If residue satisfies both the first and last parameters, then
	     it is wanted */
	  if (match_first == TRUE && match_last == TRUE)
	    wanted = TRUE;
	}
    }

  /* Return the result */
  return(wanted);
}
